Skip to content

Make native module resolution lazy and expose core internals entry point - #1316

Open
cdn34dd wants to merge 3 commits into
developfrom
carlosnogueira/draft/lazy-native-module-resolution
Open

cdn34dd wants to merge 3 commits into
developfrom
carlosnogueira/draft/lazy-native-module-resolution

Conversation

@cdn34dd

@cdn34dd cdn34dd commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

This PR makes Datadog React Native native module specs resolve lazily instead of at module importtime. It updates the core SDK wrappers to use explicit native module getter functions, adds regression tests to verify imports no longer trigger TurboModuleRegistry lookups, and exposes a scoped @datadog/mobile-react-native/internal entry point for platform packages that need shared core internals.

Motivation

Some platforms, such as Vega, need to import parts of the core package in environments where the React Native native modules are not available. Resolving TurboModules during import makes those imports fail before the caller actually uses native functionality.

Lazy native module resolution keeps the package import-safe while preserving runtime behavior on supported React Native platforms. The internal entry point also avoids unsupported deep imports from platform packages by providing a single maintained path for shared core internals.

Additional Notes

Anything else we should know when reviewing?

Review checklist (to be filled by reviewers)

  • Feature or bugfix MUST have appropriate tests
  • Make sure you discussed the feature or bugfix with the maintaining team in an Issue
  • Make sure each commit and the PR mention the Issue number (cf the CONTRIBUTING doc)
  • If this PR is auto-generated, please make sure also to manually update the code related to the change

@datadog-datadog-prod-us1

datadog-datadog-prod-us1 Bot commented Jun 28, 2026

Copy link
Copy Markdown

Pipelines  Tests

Unblock PR with BitsAI

⚠️ Warnings

Your PR has failed checks. Please review the issues below and take necessary action before merging.

🚦 2 Pipeline jobs failed

DataDog/dd-sdk-reactnative | test:js — 🔧 Needs a code fix, caused by this PR

View more details · View in GitLab

DataDog/dd-sdk-reactnative | test:lint — 🔧 Needs a code fix, caused by this PR

View more details · View in GitLab

ℹ️ Info

No other issues found (see more)

🧪 All tests passed
❄️ No new flaky tests detected

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 1c84ce5 | Docs | View more details | Give us feedback!

@cdn34dd
cdn34dd force-pushed the carlosnogueira/draft/lazy-native-module-resolution branch from 5db666e to 1c84ce5 Compare September 15, 2026 16:25
@cdn34dd
cdn34dd marked this pull request as ready for review September 15, 2026 16:26
@cdn34dd
cdn34dd requested review from a team as code owners September 15, 2026 16:26
@cdn34dd
cdn34dd requested review from sameerank and vjfridge and a lite review from Copilot and removed request for a team September 15, 2026 16:26
@cdn34dd

cdn34dd commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 15, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-15T16:30:43.989106Z 1c84ce5 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1c84ce5252

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
private nativeRum: DdNativeRumType = require('../specs/NativeDdRum')
.default;
private nativeRum: DdNativeRumType = getNativeDdRum() as DdNativeRumType;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Defer native getters until wrapper methods run

On platforms where a TurboModule lookup is unsupported or throws, imports remain unsafe because DdRum is created through getGlobalInstance during module evaluation and this field immediately calls getNativeDdRum(). The new ./internal entry reaches this wrapper through DdRumResourceTracking -> ResourceReporter -> DdRum, while the public barrel imports it directly; DdLogs, DdTrace, and DdFlags have the same eager field initialization. Resolve the module only when a wrapper operation actually needs it, otherwise both advertised import-safe entry points still perform native lookups.

Useful? React with 👍 / 👎.

Comment on lines +144 to +146
export const getNativeDdSdk = (): Spec | null => {
if (cachedModule === undefined) {
cachedModule = TurboModuleRegistry.get<Spec>('DdSdk');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve Codegen-recognizable default exports

When React Native Codegen processes the configured src/specs directory for a new-architecture build, it expects each TurboModule spec to expose its registry call as the module's default export; moving the only call into this named getter leaves the Spec undiscoverable. The same change was made in all five NativeDd* files, so Codegen can no longer generate the NativeDdSdkSpec, NativeDdRumSpec, and related native base classes consumed by the checked-in Android/iOS implementations.

Useful? React with 👍 / 👎.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved critical and moderate findings affect import safety, codegen compatibility, runtime compatibility, and regression-test validity.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

This PR makes native TurboModule resolution lazy and adds a supported internal entry point for shared core internals.

Changes:

  • Adds cached lazy native-module getters.
  • Updates SDK wrappers and telemetry integrations.
  • Exposes @datadog/mobile-react-native/internal.
  • Adds import-safety and internal-entry regression tests.
File summaries
File Change
packages/core/src/trace/DdTrace.ts Uses the native trace getter.
packages/core/src/specs/NativeDdTrace.ts Adds lazy trace resolution.
packages/core/src/specs/NativeDdSdk.ts Adds lazy SDK resolution.
packages/core/src/specs/NativeDdRum.ts Adds lazy RUM resolution.
packages/core/src/specs/NativeDdLogs.ts Adds lazy logs resolution.
packages/core/src/specs/NativeDdFlags.ts Adds lazy flags resolution.
packages/core/src/sdk/DdSdkInternal.ts Adds lazy SDK access.
packages/core/src/sdk/DatadogEventEmitter/DatadogDefaultEventEmitter.tsx Uses the SDK getter fallback.
packages/core/src/rum/instrumentation/interactionTracking/DdBabelInteractionTracking.ts Resolves SDK telemetry lazily.
packages/core/src/rum/instrumentation/interactionTracking/__tests__/DdBabelInteractionTracking.test.ts Updates SDK mocking.
packages/core/src/rum/DdRum.ts Uses the native RUM getter.
packages/core/src/logs/DdLogs.ts Uses the native logs getter.
packages/core/src/internal.ts Adds shared internal exports.
packages/core/src/flags/FlagsClient.ts Uses the native flags getter.
packages/core/src/flags/DdFlags.ts Uses the native flags getter.
packages/core/src/__tests__/internalEntry.test.ts Tests internal exports and import safety.
packages/core/src/__tests__/importSafety.test.ts Tests lazy native resolution.
packages/core/package.json Exposes the ./internal subpath.
Review details

Suppressed comments (1)

packages/core/src/specs/NativeDdFlags.ts:41

  • This removes the default export, but packages/react-native-openfeature/src/__tests__/offlineProvider.integration.test.ts:21 still mocks NativeDdFlags with only default. When the core barrel constructs DdFlagsWrapper, getNativeDdFlags is undefined and the integration test fails during import. Update that repository mock to provide the named getter and return the stub module.
export const getNativeDdFlags = (): Spec | null => {
  • Files reviewed: 18/18 changed files
  • Comments generated: 12
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
private nativeFlags: DdNativeFlagsType = require('../specs/NativeDdFlags')
.default;
private nativeFlags: DdNativeFlagsType = getNativeDdFlags() as DdNativeFlagsType;
export { GlobalState } from './sdk/GlobalState/GlobalState';
export { BufferSingleton } from './sdk/DatadogProvider/Buffer/BufferSingleton';
export { DatadogProviderState } from './sdk/DatadogProvider/DatadogProviderState';
export { DdRumResourceTracking } from './rum/instrumentation/resourceTracking/DdRumResourceTracking';
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
private nativeLogs: DdNativeLogsType = require('../specs/NativeDdLogs')
.default;
private nativeLogs: DdNativeLogsType = getNativeDdLogs() as DdNativeLogsType;
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
private nativeRum: DdNativeRumType = require('../specs/NativeDdRum')
.default;
private nativeRum: DdNativeRumType = getNativeDdRum() as DdNativeRumType;
// Lazily-backed handle. Resolution happens on first property access (runtime),
// never at import — so importing this module is safe on platforms without the
// native module (e.g. Vega). Methods are bound to the resolved module.
const NativeDdSdk: DdNativeSdkType = new Proxy({} as DdNativeSdkType, {
Comment on lines +239 to +241
export const getNativeDdRum = (): Spec | null => {
if (cachedModule === undefined) {
cachedModule = TurboModuleRegistry.get<Spec>('DdRum');
Comment on lines +144 to +146
export const getNativeDdSdk = (): Spec | null => {
if (cachedModule === undefined) {
cachedModule = TurboModuleRegistry.get<Spec>('DdSdk');
Comment on lines +49 to +51
export const getNativeDdTrace = (): Spec | null => {
if (cachedModule === undefined) {
cachedModule = TurboModuleRegistry.get<Spec>('DdTrace');
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
private nativeTrace: DdNativeTraceType = require('../specs/NativeDdTrace')
.default;
private nativeTrace: DdNativeTraceType = getNativeDdTrace() as DdNativeTraceType;
Comment on lines +27 to +31
const getSpy = jest.spyOn(TurboModuleRegistry, 'get');
jest.isolateModules(() => {
require('../internal');
});
expect(getSpy).not.toHaveBeenCalled();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants